/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package tablaDeSimbolos;

import atributosDef.Registro;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.StringTokenizer;

/**
 *
 * @author Prado
 */
public class TablaDeSimbolos {
    private HashMap<String, FilaTablaDeSimbolos> tabla;

    public TablaDeSimbolos() {
        tabla = new HashMap<>();
    }
    
    public void copia(TablaDeSimbolos tablaCopia) {
        Iterator<FilaTablaDeSimbolos> it = tabla.values().iterator();
        FilaTablaDeSimbolos fila = null;
        FilaTablaDeSimbolos nueva = null;
        while(it.hasNext()){
            fila = it.next();
            nueva = new FilaTablaDeSimbolos(fila.getId(), fila.getClase(), fila.getValorConstante(), fila.getDir(), fila.getTipo());
            tablaCopia.tabla.put(fila.getId(), nueva);
        }
    }
    
    public TablaDeSimbolos anyadirTS(Registro registro, int dir){
        FilaTablaDeSimbolos nuevaFila = new FilaTablaDeSimbolos(registro.getId(),
                registro.getClase(), registro.getValor(), dir, registro.getTipo());
        tabla.put(registro.getId(), nuevaFila);
        return this;
    }
    
    public boolean existeID(Registro registro){
        return tabla.containsKey(registro.getId());
    }
    
    public boolean existeID(Registro registro, String clase){
        String id = registro.getId();
        if(clase.equals("VARIABLE")){
            return (tabla.containsKey(id) && (tabla.get(id).getClase().equals("VAR") || tabla.get(id).getClase().equals("PVAR")));
        }else if(clase.equals("CONSTANTE")){
            return (tabla.containsKey(id) && (tabla.get(id).getClase().equals("CONST")));
        }else{
            return tabla.containsKey(registro.getId());
        }
    }
    
    public boolean existeID(String id, String clase){
        if(clase.equals("VARIABLE")){
            return (tabla.containsKey(id) && (tabla.get(id).getClase().equals("VAR") || tabla.get(id).getClase().equals("PVAR")));
        }if(clase.equals("CONSTANTE")){
            return (tabla.containsKey(id) && (tabla.get(id).getClase().equals("CONST") || tabla.get(id).getClase().equals("PVAR")));
        }else{
            return tabla.containsKey(id);
        }
    }
    
    public boolean existeID(String id){
        return tabla.containsKey(id);
    }
    
    public boolean existenIDs(String id, ArrayList<String> identificadoresParam){
        String tipo = tabla.get(id).getTipo();
        StringTokenizer tokenTipo = new StringTokenizer(tipo, " <:,>");
        ArrayList<String> parametros = new ArrayList<String>();
        while (tokenTipo.hasMoreTokens()){
            if (tokenTipo.nextToken().equals("nombre")){
                String identificador = tokenTipo.nextToken();
                Iterator<String> itIDsparam = identificadoresParam.iterator();
                boolean encontrado = false;
                while (itIDsparam.hasNext()){
                    String idParam = itIDsparam.next();
                    if (identificador.equals(idParam)){
                        if(!parametros.contains(identificador)){
                            parametros.add(identificador);
                            encontrado = true;
                        }else
                            return false;
                    }
                }
                if (!encontrado)
                    return false;
            }
        }
        
        return true;
    }
    
    public int dameTamConID(String id){
        if(tabla.containsKey(id)){
            String tipo = tabla.get(id).getTipo();
            StringTokenizer tokenTipo = new StringTokenizer(tipo, " <:,>");
            int i = 0;
            String token;
            int tam = -1;
            while(tokenTipo.hasMoreTokens()){
                token = tokenTipo.nextToken();
                // Vemos si se trata de tam
                if(token.equals("tam")){
                    tam = Integer.parseInt(tokenTipo.nextToken());
                }
            }
            return tam;
        }else{
            return -1;
        }
    }
    
    public int dameTamConIDSubprog(String idSubProgram ,String idParametro){
        //Si la tabla tiene el nombre del subprograma
        if(tabla.containsKey(idSubProgram) && tabla.get(idSubProgram).getClase().equals("PROC")){
            String tipo = tabla.get(idSubProgram).getTipo();
            StringTokenizer tokenTipo = new StringTokenizer(tipo, " <:,>");
            int i = 0;
            boolean seguir = true;
            String token;
            int tam = -1;
            while(tokenTipo.hasMoreTokens() && seguir==true){
                token = tokenTipo.nextToken();
                if(token.equals("modo") && (tokenTipo.nextToken().equals("var")) ){
                    if(tokenTipo.nextToken().equals("nombre") && (tokenTipo.nextToken().equals(idParametro)) ){
                        tam = 1;
                    }
                }else if(token.equals("modo")){
                    if(tokenTipo.nextToken().equals("nombre") && (tokenTipo.nextToken().equals(idParametro)) ){
                        while(tokenTipo.hasMoreTokens() && seguir==true){
                            token = tokenTipo.nextToken();
                            // Vemos si se trata de tam
                            if(token.equals("tam")){
                                tam = Integer.parseInt(tokenTipo.nextToken());
                                seguir = false;
                            }
                        }
                    }
                }
            }
          return tam;
        }
        return -1;
    }
    
    public boolean estaPasadoPorVariableIdentificadorConIDSubprog(String idSubProgram ,String idParametro){
        //Si la tabla tiene el nombre del subprograma
        boolean pasadoPorVar = false;
        if(tabla.containsKey(idSubProgram) && tabla.get(idSubProgram).getClase().equals("PROC")){
            String tipo = tabla.get(idSubProgram).getTipo();
            StringTokenizer tokenTipo = new StringTokenizer(tipo, " <:,>");
            boolean seguir = true;
            String token;
            while(tokenTipo.hasMoreTokens() && seguir==true){
                token = tokenTipo.nextToken();
                if(token.equals("modo") && (tokenTipo.nextToken().equals("var")) ){
                    if(tokenTipo.nextToken().equals("nombre") && (tokenTipo.nextToken().equals(idParametro)) ){
                        pasadoPorVar = true;
                        seguir = false;
                    }
                }else if(token.equals("modo")){
                    if(tokenTipo.nextToken().equals("nombre") && (tokenTipo.nextToken().equals(idParametro)) ){
                        pasadoPorVar = false;
                        seguir = false;
                    }
                }
            }
        }
        return pasadoPorVar;
    }
    
    public int calculaDesplazamientoIdentificadorConIDSubprog(String idSubProgram ,String idParametro){
        //Si la tabla tiene el nombre del subprograma
        int desp = -1;
        if(tabla.containsKey(idSubProgram) && tabla.get(idSubProgram).getClase().equals("PROC")){
            String tipo = tabla.get(idSubProgram).getTipo();
            StringTokenizer tokenTipo = new StringTokenizer(tipo, " <:,>");
            boolean seguir = true;
            String token;
            while(tokenTipo.hasMoreTokens() && seguir==true){
                token = tokenTipo.nextToken();
                if(token.equals("nombre")){
                    if(tokenTipo.nextToken().equals(idParametro)){
                        tokenTipo.nextToken();
                        desp = Integer.parseInt(tokenTipo.nextToken());
                    }
                }
            }
        }
        return desp;
    }
    
    public int dameValorConID(String id){
        if(tabla.containsKey(id)){
            String valor = tabla.get(id).getValorConstante();
            try{
                return Integer.parseInt(valor);
            }catch(Exception e){
                return -1;
            }
        }else{
            return -1;
        }
    }
      
    public int calculaTamanyoTipoConID(Registro registro){
        if(!esTipoBasico(registro.getId())){
            if(tabla.containsKey(registro.getId())){
                String tipo = dameTipoConID(registro.getId());
                if(esTipoBasico(tipo)){
                    return 1;
                }else{
                    return dameTamConID(tipo);
                }
            }else{
                return -1;
            }
        }else{
            return 1;
        }
    }
    
    public int calculaTamanyoTipoConID(String id){
        if(!esTipoBasico(id)){
            if(tabla.containsKey(id)){
                String tipo = dameTipoConID(id);
                if(esTipoBasico(tipo)){
                    return 1;
                }else{
                    if(tipo.equals("tupla") || tipo.equals("array")){
                        return dameTamConID(id);
                    }else{
                        return dameTamConID(tipo);
                    }
                }
            }else{
                return -1;
            }
        }else{
            return 1;
        }
    }
    
    public boolean numerosParametrosCorrectos(String id, int numParametros){
        if(tabla.containsKey(id)){
            if (esFuncion(id)){
                String tipoFuncion = tabla.get(id).getTipo();
                int parametros = 0;
                // Vamos a tokenizar el tipo de la funcion para ver el numero de parametros:
                StringTokenizer tokenTipo = new StringTokenizer(tipoFuncion, " <:,[]>");
                
                while (tokenTipo.hasMoreTokens()){
                    String modo = tokenTipo.nextToken(); 
                    if (modo.equals("modo")){
                        if (tokenTipo.hasMoreTokens()){ // comprobamos antes de consumir otro token
                            String valorOvar = tokenTipo.nextToken();
                            // para asegurarnos de que es un parametro y no el nombre de un identificador:
                            if (valorOvar.equals("valor") || valorOvar.equals("var")){
                                parametros++; // hemos encontrado un parametro
                            }
                        }
                    }
                }
                return (parametros == numParametros);
                
            }else{
                return false;
            }
        }else{
            return false;
        }
    }
    
    public ArrayList<String> dameIDsFuncion(String id){
        if (tabla.containsKey(id)){
            if (esFuncion(id)){
                ArrayList<String> idsFuncion = new ArrayList<String>();
                String tipoFuncion = tabla.get(id).getTipo();
                StringTokenizer tokenTipo = new StringTokenizer(tipoFuncion, " <:,[]>");
                
                while (tokenTipo.hasMoreTokens()){
                    if (tokenTipo.nextToken().equals("nombre")){
                        // guardamos el nombre del identificador de la funcion
                        idsFuncion.add(tokenTipo.nextToken()); 
                    }
                }
                return idsFuncion;
                
            }else{
                return null;
            }
        }else{
            return null;
        }
    }
    
    public ArrayList<String> dameTiposFuncion(String id){
        if (tabla.containsKey(id)){
            if (esFuncion(id)){
                ArrayList<String> tiposFuncion = new ArrayList<String>();
                String tipoFuncion = tabla.get(id).getTipo();
                StringTokenizer tokenTipo = new StringTokenizer(tipoFuncion, " <:,[]>");
                
                while (tokenTipo.hasMoreTokens()){
                    if (tokenTipo.nextToken().equals("tipo")){
                        // Pasamos el campo t
                        tokenTipo.nextToken();
                        String idTipo = tokenTipo.nextToken(); // Cogemos el tipo
                        if (idTipo.equals("ref")){
                            // Pasamos el campo id
                            tokenTipo.nextToken();
                            // Sacamos el id del tipo
                            idTipo = tokenTipo.nextToken();
                            tiposFuncion.add(idTipo);
                        }else{ // Si es un tipo basico lo anyadimos al arraylist
                            tiposFuncion.add(idTipo);
                        }
                    }
                }
                return tiposFuncion;
                
            }else{
                return null;
            }
        }else{
            return null;
        }
    }
    
    public String dameTipoConID(String id){
        if(!esTipoBasico(id)){
            if(tabla.containsKey(id)){
                String tipo = tabla.get(id).getTipo();
                StringTokenizer tokenTipo = new StringTokenizer(tipo, " <:,>");
                // Pasamos el campo t
                tokenTipo.nextToken();
                // Cogemos el tipo
                String idTipo = tokenTipo.nextToken();
                // Vemos si no es un tipo básico
                if(idTipo.equals("ref")){
                    // Pasamos el campo id
                    tokenTipo.nextToken();
                    // Cogemos el tipo
                    idTipo = tokenTipo.nextToken();
                    return dameTipoConID(idTipo);
                }else if(idTipo.equals("array")){
                    return id;
                }else if(idTipo.equals("tupla")){
                    return id;
                }
                return idTipo;
            }else{
                return null;
            }
        }else{
            return id;
        }
    }
    
    public String dameTipoConID(TablaDeSimbolos tsh, String id){
        if(!esTipoBasico(id)){
            if(tabla.containsKey(id)){
                String tipo = tabla.get(id).getTipo();
                StringTokenizer tokenTipo = new StringTokenizer(tipo, " <:,>");
                // Pasamos el campo t
                tokenTipo.nextToken();
                // Cogemos el tipo
                String idTipo = tokenTipo.nextToken();
                // Vemos si no es un tipo básico
                if(idTipo.equals("ref")){
                    // Pasamos el campo id
                    tokenTipo.nextToken();
                    // Cogemos el tipo
                    idTipo = tokenTipo.nextToken();
                    return tsh.dameTipoConID(idTipo);
                }else if(idTipo.equals("array")){
                    return id;
                }else if(idTipo.equals("tupla")){
                    return id;
                }
                return idTipo;
            }else{
                return null;
            }
        }else{
            return id;
        }
    }
    
    public String dameStringTipoConID(String id){
        if (tabla.containsKey(id))
            return tabla.get(id).getTipo();
        else
            return null;
    }
    
    public String calculaTamanyoDimensionArray(String id, int dimArrayh){
        if(existeID(id, "TIPO")){
            dimArrayh++;
            ArrayList<String> tamanios = new ArrayList<>();
            String codTipo = dameStringTipoConID(id);
            StringTokenizer tokenTipo = new StringTokenizer(codTipo, " <:,[]>");
            while(tokenTipo.hasMoreTokens()){
                if(tokenTipo.nextToken().equals("tam")){
                    tamanios.add(tokenTipo.nextToken());
                }
            }
            return tamanios.get((tamanios.size()-1)-dimArrayh);
        }else{
            return String.valueOf(-1);
        }
    }
    
    public boolean quedanMasDimensiones(String id, int dimArrayh){
        if(existeID(id, "TIPO")){
            int dimensionAuxiliar = -1;
            String codTipo = dameStringTipoConID(id);
            StringTokenizer tokenTipo = new StringTokenizer(codTipo, " <:,[]>");
            while(tokenTipo.hasMoreTokens()){
                if(tokenTipo.nextToken().equals("array")){
                    dimensionAuxiliar++;
                }
            }
            return dimensionAuxiliar>dimArrayh;
        }else{
            return false;
        }
    }
    
    public boolean quedanMasDimensionesEnTupla(String id, int dimArrayh){
        if(dimArrayh!=0){
            if(existeID(id, "TIPO")){
                dimArrayh--;
                int dimensionAuxiliar = -1;
                String codTipo = dameStringTipoConID(id);
                StringTokenizer tokenTipo = new StringTokenizer(codTipo, " <:,[]>");
                while(tokenTipo.hasMoreTokens()){
                    if(tokenTipo.nextToken().equals("array")){
                        dimensionAuxiliar++;
                    }
                }
                return dimensionAuxiliar>dimArrayh;
            }else{
                return false;
            }
        }else{
            return false;
        }
    }

    public String dameTipoBaseConID(String id, String pos){
        int posicion = Integer.parseInt(pos.replace("_", ""));
        if(existeID(id, "TIPO")){
            String codTipo = dameStringTipoConID(id);
            StringTokenizer tokenTipo = new StringTokenizer(codTipo, " <:,[]>");
            //Quitamos t
            tokenTipo.nextToken();
            String tipoT = tokenTipo.nextToken();
            if(tipoT.equals("ref")){
                //Quitamos id
                tokenTipo.nextToken();
                return dameTipoBaseConID(tokenTipo.nextToken(), pos);
            }else if(tipoT.equals("tupla")){
                while(posicion>=0 && tokenTipo.hasMoreTokens()){
                    if(tokenTipo.nextToken().equals("tipo")){
                        posicion--;
                    }
                }
                if(posicion==-1){
                    //Quitamos t
                    tokenTipo.nextToken();
                    String tipoTipo = tokenTipo.nextToken();
                    if(tipoTipo.equals("ref")){
                        //Quitamos id
                        tokenTipo.nextToken();
                        return dameTipoConID(tokenTipo.nextToken());
                    }else{
                        return tipoTipo;
                    }
                }else{
                    return "tError";
                }
            }else{
                return "tError";
            }
        }else{
            return "tError";
        }
    }
    
    public boolean esConstante(String id){
        if(tabla.containsKey(id)){
            return tabla.get(id).getClase().equals("CONST");
        }else{
            return false;
        }
    }
    
    public int dameDirConID(String id){
        if(tabla.containsKey(id)){
            return tabla.get(id).getDir();
        }else{
            return -1;
        }
    }
    
    public boolean compatiblesTipoNumericoValor(String id){
        if(tabla.containsKey(id)){
            String tipo = tabla.get(id).getTipo();
            StringTokenizer tokenTipo = new StringTokenizer(tipo, " <:,>");
            // Pasamos el campo t
            tokenTipo.nextToken();
            // Cogemos el tipo
            String idTipo = tokenTipo.nextToken();
            // Vemos si se trata de un tipo definido por el usuario
            if(idTipo.equals("ref")){
                return compatiblesTipoNumericoValor(dameTipoConID(idTipo));
            }else{
                return (idTipo.equals("natural"));
            }
        }else{
            return false;
        }
    }
    
    public boolean esVariable(TablaDeSimbolos tslh, String id){
        if(tslh != null){
            if(tslh.tabla.containsKey(id)){
                return (tslh.tabla.get(id).getClase().equals("VAR") || tslh.tabla.get(id).getClase().equals("PVAR"));
            }else{
                if(tabla.containsKey(id)){
                    return tabla.get(id).getClase().equals("VAR");
                }else{
                    return false;
                }
            }
        }else{
            if(tabla.containsKey(id)){
                return tabla.get(id).getClase().equals("VAR");
            }else{
                return false;
            }
        }
    }
    
    public boolean esPVar(String id){
        if(tabla.containsKey(id)){
            return (tabla.get(id).getClase().equals("PVAR"));
        }else{
            return false;
        }
    }
    
    public boolean esVariable(String id){
        if(tabla.containsKey(id)){
            return tabla.get(id).getClase().equals("VAR");
        }else{
            return false;
        }
    }
    
    public boolean esFuncion(String id){
        if(tabla.containsKey(id)){
            return tabla.get(id).getClase().equals("PROC");
        }else{
            return false;
        }
    }
    
    public boolean esTipoBasico(String tipo){
        return tipo.equals("natural") || tipo.equals("integer") || tipo.equals("float") || tipo.equals("character") || tipo.equals("boolean");
    }
    
    public int dameDirConId(String id){
        if(tabla.containsKey(id)){
            return tabla.get(id).getDir();
        }else{
            return -1;
        }
    }
    
    public int calculaDesplazamiento(String id, String natural){
        natural = natural.replace("_", "");
        if(tabla.containsKey(id)){
            try{
                int dim = Integer.parseInt(natural);
                String tipo = tabla.get(id).getTipo();
                StringTokenizer tokenTipo = new StringTokenizer(tipo, " <:,>");
                int i = 0;
                String idTipo;
                while(tokenTipo.hasMoreTokens() && i<=dim){
                    idTipo = tokenTipo.nextToken();
                    // Vemos si no es un tipo básico
                    if(idTipo.equals("desp")){
                        i++;
                    }
                }
                try{
                    int desp = Integer.parseInt(tokenTipo.nextToken());
                    return desp;
                }catch(Exception e){
                    return -1;
                }
            }catch(Exception e){
                return -1;
            }
        }else{
            return -1;
        }
    }
    
    public boolean accesoTuplaFueraRango(String tipo, String natural){
        int posicion = Integer.parseInt(natural.replace("_", ""));
        if(tipo.equals("tError")){
            return false;
        }else if(existeID(tipo, "TIPO")){
            String codTipo = dameStringTipoConID(tipo);
            StringTokenizer tokenTipo = new StringTokenizer(codTipo, " <:,>");
            while(!tokenTipo.nextToken().equals("nElems"));
            return Integer.parseInt(tokenTipo.nextToken().toString()) <=posicion;
        }else{
            return false;
        }
    }
    
    public boolean dimensionArrayErronea(String id, int dimArrayh){
        if(existeID(id, "TIPO")){
            int dimensionAuxiliar = -1;
            String codTipo = dameStringTipoConID(id);
            StringTokenizer tokenTipo = new StringTokenizer(codTipo, " <:,[]>");
            while(tokenTipo.hasMoreTokens()){
                if(tokenTipo.nextToken().equals("array")){
                    dimensionAuxiliar++;
                }
            }
            return !(dimensionAuxiliar>=dimArrayh);
        }else{
            return false;
        }
    }
    
    public String dameTipoBaseConID(String id){
        if(existeID(id, "TIPO")){
            String codTipo = dameStringTipoConID(id);
            StringTokenizer tokenTipo = new StringTokenizer(codTipo, " <:,>");
            //Quitamos t
            tokenTipo.nextToken();
            String tipoT = tokenTipo.nextToken();
            if(tipoT.equals("ref")){
                //Quitamos id
                tokenTipo.nextToken();
                return dameTipoBaseConID(tokenTipo.nextToken());
            }else if(tipoT.equals("array")){
                while(!tokenTipo.nextToken().equals("tBase"));
                //Quitamos t
                tokenTipo.nextToken();
                String refOTipoB = tokenTipo.nextToken();
                if(refOTipoB.equals("ref")){
                    //Quitamos id
                    tokenTipo.nextToken();
                    return dameTipoConID(tokenTipo.nextToken());
                }else if(refOTipoB.equals("array")){
                    return dameTipoBaseArrayDimensionN(tokenTipo);
                }else{
                    return refOTipoB;
                }
            }else if(tipoT.equals("tupla")){
                return id;
            }else{
                return "tError";
            }
        }else if(esTipoBasico(id)){
            return id;
        }else{
            return "tError";
        }
    }
    
    public String dameTipoBaseArrayDimensionN(StringTokenizer tokenTipo){
        while(!tokenTipo.nextToken().equals("tBase"));
        //Quitamos t
        tokenTipo.nextToken();
        String refOTipoB = tokenTipo.nextToken();
        if(refOTipoB.equals("ref")){
            //Quitamos id
            tokenTipo.nextToken();
            return dameTipoConID(tokenTipo.nextToken());
        }else if(refOTipoB.equals("array")){
            return dameTipoBaseArrayDimensionN(tokenTipo);
        }else{
            return refOTipoB;
        }
    }
    
    public int dameTamTipoBaseConID(String id){
        if(!esTipoBasico(id)){
            if(tabla.containsKey(id)){
                String tipo = dameTipoBaseConID(id);
                return calculaTamanyoTipoConID(tipo);
            }else{
                return -1;
            }
        }else{
            return 1;
        }
    } 
    
    public boolean esTipoTupla(String tipo){
        if(tipo.equals("tError")){
            return false;
        }else if(tipo.equals("tupla")){
            return true;
        }else{
            if(existeID(tipo, "TIPO")){
            String codTipo = dameStringTipoConID(tipo);
                StringTokenizer tokenTipo = new StringTokenizer(codTipo, " <:,>");
                tokenTipo.nextToken();
                String tipoT = tokenTipo.nextToken();
                if(tipoT.equals("ref")){
                    tokenTipo.nextToken();
                    return esTipoTupla(tokenTipo.nextToken());
                }else if(tipoT.equals("tupla")){
                    return true;
                }else{
                    return false;
                }
            }else{
                return false;
            }
        }
    }
    
    public boolean esTipoArray(String tipo){
        if(tipo.equals("tError")){
            return false;
        }else if(tipo.equals("array")){
            return true;
        }else{
            if(existeID(tipo, "TIPO")){
                String codTipo = dameStringTipoConID(tipo);
                StringTokenizer tokenTipo = new StringTokenizer(codTipo, " <:,>");
                tokenTipo.nextToken();
                String tipoT = tokenTipo.nextToken();
                if(tipoT.equals("ref")){
                    tokenTipo.nextToken();
                    return esTipoArray(tokenTipo.nextToken());
                }else if(tipoT.equals("array")){
                    return true;
                }else{
                    return false;
                }
            }else{
                return false;
            }
        }
    }
}
